package com.ourteam.sourcecode.utils;

import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;

import org.apache.commons.collections.IteratorUtils;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.filefilter.IOFileFilter;
import org.apache.commons.io.filefilter.TrueFileFilter;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.SystemUtils;

import net.sourceforge.pmd.PMD;
import net.sourceforge.pmd.PMDConfiguration;
import net.sourceforge.pmd.Report;
import net.sourceforge.pmd.RuleContext;
import net.sourceforge.pmd.RuleSet;
import net.sourceforge.pmd.RuleSetFactory;
import net.sourceforge.pmd.RuleViolation;
import net.sourceforge.pmd.lang.LanguageRegistry;
import net.sourceforge.pmd.lang.LanguageVersion;
import net.sourceforge.pmd.renderers.AbstractRenderer;
import net.sourceforge.pmd.renderers.Renderer;
import net.sourceforge.pmd.util.datasource.DataSource;
import net.sourceforge.pmd.util.datasource.FileDataSource;

public class SourceCodeCheckUtils {

	public static SourceCodeCheckRuleSet[] batchCheckSourceCode(final String inputFilePath, final String[] rulePriority,
			Map<String, String[]> fileTypeLanguageMap, final ISourceCodeCheckListtener listtener) throws Exception {

		final List<String> languageList = new ArrayList<>();

		final List<String> fileTypeList = new ArrayList<>();

		for (Iterator<String> iterator = fileTypeLanguageMap.keySet().iterator(); iterator.hasNext();) {
			String fileType = iterator.next();
			fileTypeList.add(fileType);
			languageList.add(fileTypeLanguageMap.get(fileType)[0]);
		}

		PMDConfiguration configuration = new PMDConfiguration();

		configuration.setSourceEncoding("UTF-8");

		configuration.setClassLoader(Thread.currentThread().getContextClassLoader());

		RuleSetFactory ruleSetFactory = new RuleSetFactory();

		ruleSetFactory.setClassLoader(configuration.getClassLoader());

		List<String> ruleSets = new ArrayList<>();

		final Map<String, SourceCodeCheckRuleSet> checkResults = new HashMap<String, SourceCodeCheckRuleSet>();

		for (Iterator<RuleSet> iterator = ruleSetFactory.getRegisteredRuleSets(); iterator.hasNext();) {

			RuleSet ruleSet = iterator.next();

			for (Iterator<String> iterator2 = languageList.iterator(); iterator2.hasNext();) {
				String languageName = iterator2.next();

				if (StringUtils.startsWith(ruleSet.getFileName(), "rulesets/" + languageName)) {

					ruleSets.add(ruleSet.getFileName());

					SourceCodeCheckRuleSet checkRuleSet = new SourceCodeCheckRuleSet();

					checkRuleSet.setName(ruleSet.getName());

					checkRuleSet.setDescription(ruleSet.getDescription());

					checkResults.put(ruleSet.getName(), checkRuleSet);
				}
			}
		}

		configuration.setRuleSets(StringUtils.join(ruleSets, ","));

		ruleSetFactory.setWarnDeprecated(false);

		final List<DataSource> files = new ArrayList<DataSource>();

		FileUtils.listFilesAndDirs(new File(inputFilePath), new IOFileFilter() {

			@Override
			public boolean accept(File arg0, String arg1) {
				return accept(new File(arg0, arg1));
			}

			@Override
			public boolean accept(File arg0) {

				if (fileTypeList.contains(FilenameUtils.getExtension(arg0.getName()))) {
					files.add(new FileDataSource(arg0));
				}
				return false;
			}
		}, TrueFileFilter.INSTANCE);

		if (listtener != null) {
			listtener.setTotal(files.size());
		}

		RuleContext ctx = new RuleContext();

		Report errorReport = new Report();

		ctx.setReport(errorReport);

		Renderer logRenderer = new AbstractRenderer("log", "Logging renderer") {
			public void start() {
				if (listtener != null) {
					listtener.start();
				}
			}

			public void startFileAnalysis(DataSource dataSource) {
				System.out.println("Processing file " + dataSource.getNiceFileName(false, inputFilePath));
				if (listtener != null) {
					listtener.setProgress(files.indexOf(dataSource));
					listtener.setMessage(
							StringUtils.abbreviateMiddle(dataSource.getNiceFileName(false, inputFilePath), "...", 50));
				}
			}

			public void renderFileReport(Report report) {

				for (Iterator<RuleViolation> iterator = report.getViolationTree().iterator(); iterator.hasNext();) {
					RuleViolation ruleViolation = iterator.next();

					if (ArrayUtils.isNotEmpty(rulePriority) && ArrayUtils.contains(rulePriority,
							ruleViolation.getRule().getPriority().getName()) == false) {
						continue;
					}

					List<SourceCodeCheckResult> results = checkResults.get(ruleViolation.getRule().getRuleSetName())
							.getCheckResults();

					SourceCodeCheckResult checkResult = new SourceCodeCheckResult();

					checkResult.setBeginColumn(ruleViolation.getBeginColumn());
					checkResult.setBeginLine(ruleViolation.getBeginLine());
					checkResult.setClassName(ruleViolation.getClassName());
					checkResult.setDescription(ruleViolation.getDescription());
					checkResult.setEndColumn(ruleViolation.getEndColumn());
					checkResult.setEndLine(ruleViolation.getEndLine());
					checkResult.setFileName(ruleViolation.getFilename());
					checkResult.setMethodName(ruleViolation.getMethodName());
					checkResult.setPackageName(ruleViolation.getPackageName());
					checkResult.setVariableName(ruleViolation.getVariableName());
					checkResult.setPriority(ruleViolation.getRule().getPriority().getName());

					List<String> examples = ruleViolation.getRule().getExamples();
					StringBuffer buffer = new StringBuffer();
					for (Iterator<String> examplesIterator = examples.iterator(); examplesIterator.hasNext();) {
						String example = examplesIterator.next();
						buffer.append(example).append(SystemUtils.LINE_SEPARATOR);
					}

					checkResult.setExample(buffer.toString());

					checkResult.setMessage(ruleViolation.getRule().getMessage());

					results.add(checkResult);

				}
			}

			public void end() {
				if (listtener != null) {
					listtener.finished();
				}
			}

			public String defaultFileExtension() {
				return null;
			} // not relevant
		};

		List<Renderer> renderers = new ArrayList<>();

		renderers.add(logRenderer);

		PMD.processFiles(configuration, ruleSetFactory, files, ctx, renderers);

		return (SourceCodeCheckRuleSet[]) IteratorUtils.toArray(checkResults.values().iterator(),
				SourceCodeCheckRuleSet.class);
	}

	public static SourceCodeCheckRuleSet[] checkSourceCode(final String inputFilePath, String languageName,
			String languageVersion) throws Exception {

		PMDConfiguration configuration = new PMDConfiguration();

		configuration.setSourceEncoding("UTF-8");

		configuration.setClassLoader(Thread.currentThread().getContextClassLoader());

		LanguageVersion sourcelanguageVersion = null;

		if (StringUtils.isNotBlank(languageVersion)) {
			sourcelanguageVersion = LanguageRegistry
					.findLanguageVersionByTerseName(languageName + " " + languageVersion);
		} else {
			sourcelanguageVersion = LanguageRegistry.findLanguageVersionByTerseName(languageName);
		}

		configuration.setInputFilePath(inputFilePath);

		configuration.setDefaultLanguageVersion(sourcelanguageVersion);

		RuleSetFactory ruleSetFactory = new RuleSetFactory();

		ruleSetFactory.setClassLoader(configuration.getClassLoader());

		List<String> ruleSets = new ArrayList<>();

		final Map<String, SourceCodeCheckRuleSet> checkResults = new HashMap<String, SourceCodeCheckRuleSet>();

		for (Iterator<RuleSet> iterator = ruleSetFactory.getRegisteredRuleSets(); iterator.hasNext();) {

			RuleSet ruleSet = iterator.next();

			if (StringUtils.startsWith(ruleSet.getFileName(), "rulesets/" + languageName)) {

				ruleSets.add(ruleSet.getFileName());

				SourceCodeCheckRuleSet checkRuleSet = new SourceCodeCheckRuleSet();

				checkRuleSet.setName(ruleSet.getName());

				checkRuleSet.setDescription(ruleSet.getDescription());

				checkResults.put(ruleSet.getName(), checkRuleSet);
			}
		}

		configuration.setRuleSets(StringUtils.join(ruleSets, ","));

		ruleSetFactory.setWarnDeprecated(false);

		RuleContext ctx = new RuleContext();

		Report errorReport = new Report();

		ctx.setReport(errorReport);

		ctx.setLanguageVersion(sourcelanguageVersion);

		final AtomicInteger reportSize = new AtomicInteger();

		List<DataSource> files = new ArrayList<DataSource>();

		FileDataSource fileDataSource = new FileDataSource(new File(inputFilePath));

		files.add(fileDataSource);

		Renderer logRenderer = new AbstractRenderer("log", "Logging renderer") {
			public void start() {
			}

			public void startFileAnalysis(DataSource dataSource) {
				System.out.println("Processing file " + dataSource.getNiceFileName(false, inputFilePath));
			}

			public void renderFileReport(Report report) {
				int size = report.size();

				for (Iterator<RuleViolation> iterator = report.getViolationTree().iterator(); iterator.hasNext();) {
					RuleViolation ruleViolation = iterator.next();

					List<SourceCodeCheckResult> results = checkResults.get(ruleViolation.getRule().getRuleSetName())
							.getCheckResults();

					SourceCodeCheckResult checkResult = new SourceCodeCheckResult();

					checkResult.setBeginColumn(ruleViolation.getBeginColumn());
					checkResult.setBeginLine(ruleViolation.getBeginLine());
					checkResult.setClassName(ruleViolation.getClassName());
					checkResult.setDescription(ruleViolation.getDescription());
					checkResult.setEndColumn(ruleViolation.getEndColumn());
					checkResult.setEndLine(ruleViolation.getEndLine());
					checkResult.setFileName(ruleViolation.getFilename());
					checkResult.setMethodName(ruleViolation.getMethodName());
					checkResult.setPackageName(ruleViolation.getPackageName());
					checkResult.setVariableName(ruleViolation.getVariableName());
					checkResult.setPriority(ruleViolation.getRule().getPriority().getName());

					List<String> examples = ruleViolation.getRule().getExamples();
					StringBuffer buffer = new StringBuffer();
					for (Iterator<String> examplesIterator = examples.iterator(); examplesIterator.hasNext();) {
						String example = examplesIterator.next();
						buffer.append(example).append(SystemUtils.LINE_SEPARATOR);
					}

					checkResult.setExample(buffer.toString());

					checkResult.setMessage(ruleViolation.getRule().getMessage());

					results.add(checkResult);

				}

				if (size > 0) {
					reportSize.addAndGet(size);
				}
			}

			public void end() {
			}

			public String defaultFileExtension() {
				return null;
			} // not relevant
		};

		List<Renderer> renderers = new ArrayList<>();

		renderers.add(logRenderer);

		try {
			PMD.processFiles(configuration, ruleSetFactory, files, ctx, renderers);

			return (SourceCodeCheckRuleSet[]) IteratorUtils.toArray(checkResults.values().iterator(),
					SourceCodeCheckRuleSet.class);

		} catch (RuntimeException pmde) {
			throw pmde;
		}

	}
}
